<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>5.模块化组织 | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/111.225c257a.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>Angular框架解读</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-0-prestart.html" class="sidebar-link">0.预热篇</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-metadata.html" class="sidebar-link">1.元数据和装饰器</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-dom-define.html" class="sidebar-link">2.视图抽象定义</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html" class="sidebar-link">3.Zone区域之zone.js</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html" class="sidebar-link">4.Zone区域之ngZone</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html" aria-current="page" class="active sidebar-link">5.模块化组织</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html#angular-中的模块" class="sidebar-link">Angular 中的模块</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html#模块能力" class="sidebar-link">模块能力</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html#总结" class="sidebar-link">总结</a></li></ul></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html" class="sidebar-link">6.依赖注入的基本概念</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html" class="sidebar-link">7.多级依赖注入设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-3-bootstrap.html" class="sidebar-link">8.依赖注入的引导过程</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-0-design.html" class="sidebar-link">9.Ivy编译器整体设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-1-view-data-and-node-injector.html" class="sidebar-link">10.Ivy编译器的视图数据和依赖解析</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>玩转 Angular</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Cycle.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>D3.js 和 Tree</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Three.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>BOX2D.js 文档与教程</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>作为“为大型前端项目”而设计的前端框架，Angular 其实有许多值得参考和学习的设计，本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的模块设计、模块化组织等内容进行介绍。</p> <h2 id="angular-中的模块"><a href="#angular-中的模块" class="header-anchor">#</a> Angular 中的模块</h2> <p>在 AngularJS 升级到 Angular（2+ 版本）之后，引入了模块的设计。在我们进行 Angular 应用开发时，总是离不开模块，包括 Angular 自带的通用模块，以及应用启动的根模块等等。</p> <p>说到模块化，前端开发首先会想到 <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/" target="_blank" rel="noopener noreferrer">ES6 的模块<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>，这两者其实并没有什么关联：</p> <ul><li>ES6 模块以文件为单位；Angular 模块则是以 NgModule 为单位。</li> <li>ES6 模块用于跨文件的功能调用；Angular 模块用于组织有特定意义的功能块。</li> <li>ES6 模块在编译阶段确认各个模块的依赖关系，模块间关系扁平；Angular 模块则可以带有深度的层次结构。</li></ul> <h3 id="ngmodules-定义"><a href="#ngmodules-定义" class="header-anchor">#</a> NgModules 定义</h3> <p>在 Angular 中，会使用 NgModules 来进行模块组织和管理。</p> <p>NgModule 是一个带有<code>@NgModule</code>装饰器的类，<code>@NgModule</code>的参数是一个元数据对象，用于描述如何编译组件的模板，以及如何在运行时创建注入器。 它会标出该模块自己的组件、指令和管道，通过<code>exports</code>属性公开其中的一部分，以便外部组件使用它们。 关于元数据和装饰器，可参考<a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-metadata.html">Angular框架解读--元数据和装饰器</a>一节。</p> <p>NgModule 把组件、指令和管道打包成内聚的功能块，每个模块聚焦于一个特性区域、业务领域、工作流或通用工具。运行时，模块相关的信息存储在<code>NgModuleDef</code>中：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token comment">// NgModuleDef 是运行时用于组装组件、指令、管道和注入器的内部数据结构</span>
<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">NgModuleDef<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span> <span class="token punctuation">{</span>
  <span class="token comment">// 表示模块的令牌，由DI使用</span>
  type<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  <span class="token comment">// 要引导的组件列表</span>
  bootstrap<span class="token operator">:</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 此模块声明的组件、指令和管道的列表</span>
  declarations<span class="token operator">:</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 此模块导入的模块列表或 ModuleWithProviders </span>
  imports<span class="token operator">:</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 该模块导出的模块、ModuleWithProviders、组件、指令或管道的列表</span>
  exports<span class="token operator">:</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Type<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 为该模块计算的 transitiveCompileScopes 的缓存值</span>
  transitiveCompileScopes<span class="token operator">:</span> NgModuleTransitiveScopes<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token comment">// 声明 NgModule 中允许的元素的一组模式</span>
  schemas<span class="token operator">:</span> SchemaMetadata<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token comment">// 应为其注册模块的唯一ID</span>
  id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>宏观来讲，NgModule 是组织 Angular 应用的一种方式，它们通过<code>@NgModule</code>装饰器中的元数据来实现这一点，这些元数据可以分成三类：</p> <ul><li>静态的：编译器配置，通过<code>declarations</code>数组来配置。用于告诉编译器指令的选择器，并通过选择器匹配的方式，决定要把该指令应用到模板中的什么位置</li> <li>运行时：通过<code>providers</code>数组提供给注入器的配置</li> <li>组合/分组：通过<code>imports</code>和<code>exports</code>数组来把多个 NgModule 放在一起，并让它们可用</li></ul> <p>可以看到，一个 NgModules 模块通过<code>declarations</code>声明该模块的组件、指令和管道，同时通过<code>import</code>导入其他模块和服务，以此来构成内聚的功能块。NgModule 还能把一些服务提供者添加到应用的依赖注入器中，具体可参考后续依赖注入部分内容。</p> <h3 id="模块化组织"><a href="#模块化组织" class="header-anchor">#</a> 模块化组织</h3> <p>每个 Angular 应用有至少一个模块，该模块称为根模块（AppModule）。Angular 应用的启动，便是由根模块开始的，可以参考后续的依赖注入的引导过程内容。</p> <p>对于一个简单的 Angular 应用来说，一个根模块就足以管理整个应用的功能。对于复杂的应用来说，则可以根据功能来划分成不同的模块，每个模块可专注于某项功能或业务领域、工作流程或导航流程、通用工具集，或者成为一个或多个服务提供者。</p> <p>在 Angular 中，推荐的模块可以根据类型划分为：</p> <ul><li>领域模块：领域模块围绕特性、业务领域或用户体验进行组织</li> <li>带路由的模块：模块的顶层组件充当路由器访问这部分路由时的目的地</li> <li>路由配置模块：路由配置模块为另一个模块提供路由配置</li> <li>服务模块：服务模块提供实用服务，比如数据访问和消息传递</li> <li>小部件：小部件模块可以为其它模块提供某些组件、指令或管道</li> <li>共享模块：共享模块可以为其它的模块提供组件，指令和管道的集合</li></ul> <p>可见，模块可以以不同的方式进行组织，可以包括组件、指令和管道和服务，也可以仅提供其中一种，比如<code>HttpClientModule</code>便是仅由提供者组织的模块：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token decorator"><span class="token at operator">@</span><span class="token function">NgModule</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token comment">// XSRF 保护的可选配置</span>
  imports<span class="token operator">:</span> <span class="token punctuation">[</span>
    HttpClientXsrfModule<span class="token punctuation">.</span><span class="token function">withOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      cookieName<span class="token operator">:</span> <span class="token string">'XSRF-TOKEN'</span><span class="token punctuation">,</span>
      headerName<span class="token operator">:</span> <span class="token string">'X-XSRF-TOKEN'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token comment">// 配置 DI，并在其中将其与 HTTP 通信的支持服务一起导入</span>
  providers<span class="token operator">:</span> <span class="token punctuation">[</span>
    HttpClient<span class="token punctuation">,</span>
    <span class="token punctuation">{</span>provide<span class="token operator">:</span> HttpHandler<span class="token punctuation">,</span> useClass<span class="token operator">:</span> HttpInterceptingHandler<span class="token punctuation">}</span><span class="token punctuation">,</span>
    HttpXhrBackend<span class="token punctuation">,</span>
    <span class="token punctuation">{</span>provide<span class="token operator">:</span> HttpBackend<span class="token punctuation">,</span> useExisting<span class="token operator">:</span> HttpXhrBackend<span class="token punctuation">}</span><span class="token punctuation">,</span>
    BrowserXhr<span class="token punctuation">,</span>
    <span class="token punctuation">{</span>provide<span class="token operator">:</span> XhrFactory<span class="token punctuation">,</span> useExisting<span class="token operator">:</span> BrowserXhr<span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">HttpClientModule</span> <span class="token punctuation">{</span>
<span class="token punctuation">}</span>
</code></pre></div><h2 id="模块能力"><a href="#模块能力" class="header-anchor">#</a> 模块能力</h2> <p>现在我们已经知道，NgModule 是把组件、指令和管道打包成内聚的功能块，那么在 NgModule 里面是怎么管理这些内容的呢？</p> <h3 id="模块与组件"><a href="#模块与组件" class="header-anchor">#</a> 模块与组件</h3> <p>在 Angular 中，每个组件都应该（且只能）声明（declare）在一个 NgModule 类中。属于相同 NgModule 的组件会共享同一个编译上下文环境，该环境信息由<code>LocalModuleScopeRegistry</code>维护：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LocalModuleScopeRegistry</span> <span class="token keyword">implements</span> <span class="token class-name">MetadataRegistry</span><span class="token punctuation">,</span> ComponentScopeReader <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token comment">// 从当前编译单元到声明它们的 NgModule 的组件映射</span>
  <span class="token keyword">private</span> declarationToModule <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>ClassDeclaration<span class="token punctuation">,</span> DeclarationData<span class="token operator">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 这从指令/管道类映射到声明该指令/管道的每个 NgModule 的数据映射</span>
  <span class="token keyword">private</span> duplicateDeclarations <span class="token operator">=</span>
      <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>ClassDeclaration<span class="token punctuation">,</span> Map<span class="token operator">&lt;</span>ClassDeclaration<span class="token punctuation">,</span> DeclarationData<span class="token operator">&gt;&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> moduleToRef <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>ClassDeclaration<span class="token punctuation">,</span> Reference<span class="token operator">&lt;</span>ClassDeclaration<span class="token operator">&gt;&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 为当前程序中声明的每个 NgModule 计算的 LocalModuleScope 的缓存</span>
  <span class="token keyword">private</span> cache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>ClassDeclaration<span class="token punctuation">,</span> LocalModuleScope<span class="token operator">|</span><span class="token keyword">null</span><span class="token operator">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  
  <span class="token comment">// 将 NgModule 的数据添加到注册表中</span>
  <span class="token function">registerNgModuleMetadata</span><span class="token punctuation">(</span>data<span class="token operator">:</span> NgModuleMeta<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token comment">// 为组件获取作用域</span>
  <span class="token function">getScopeForComponent</span><span class="token punctuation">(</span>clazz<span class="token operator">:</span> ClassDeclaration<span class="token punctuation">)</span><span class="token operator">:</span> LocalModuleScope<span class="token operator">|</span><span class="token keyword">null</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> scope <span class="token operator">=</span> <span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>declarationToModule<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>clazz<span class="token punctuation">)</span> <span class="token operator">?</span>
        <span class="token keyword">null</span> <span class="token operator">:</span>
        <span class="token comment">// 返回 NgModule 的作用域</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getScopeOfModule</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>declarationToModule<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>clazz<span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">.</span>ngModule<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> scope<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 收集模块及其指令/管道的注册数据，并将其转换为完整的 LocalModuleScope</span>
  <span class="token function">getScopeOfModule</span><span class="token punctuation">(</span>clazz<span class="token operator">:</span> ClassDeclaration<span class="token punctuation">)</span><span class="token operator">:</span> LocalModuleScope<span class="token operator">|</span><span class="token keyword">null</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>moduleToRef<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>clazz<span class="token punctuation">)</span> <span class="token operator">?</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getScopeOfModuleReference</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>moduleToRef<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>clazz<span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">)</span> <span class="token operator">:</span>
        <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>LocalModuleScopeRegistry</code>类实现 NgModule 声明、导入和导出的逻辑，并且可以为给定组件生成在该组件的模板中“可见”的一组指令和管道。它收集有关本地的 NgModules，指令、组件和管道的信息，并且可以生成<code>LocalModuleScope</code>，概括了组件的编译范围。</p> <p>每个 NgModule 在编译<code>@NgModule</code>装饰器的元数据时，会向<code>LocalModuleScopeRegistry</code>注册该模块的信息：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NgModuleDecoratorHandler</span> <span class="token keyword">implements</span>
    <span class="token class-name">DecoratorHandler<span class="token operator">&lt;</span>Decorator<span class="token punctuation">,</span> NgModuleAnalysis<span class="token punctuation">,</span> NgModuleResolution<span class="token operator">&gt;</span></span> <span class="token punctuation">{</span>
  <span class="token function">register</span><span class="token punctuation">(</span>node<span class="token operator">:</span> ClassDeclaration<span class="token punctuation">,</span> analysis<span class="token operator">:</span> NgModuleAnalysis<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token comment">// 这样可以确保在 compile() 阶段，模块的元数据可用于选择器作用域计算</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>metaRegistry<span class="token punctuation">.</span><span class="token function">registerNgModuleMetadata</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      ref<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Reference</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">,</span>
      schemas<span class="token operator">:</span> analysis<span class="token punctuation">.</span>schemas<span class="token punctuation">,</span>
      declarations<span class="token operator">:</span> analysis<span class="token punctuation">.</span>declarations<span class="token punctuation">,</span>
      imports<span class="token operator">:</span> analysis<span class="token punctuation">.</span>imports<span class="token punctuation">,</span>
      exports<span class="token operator">:</span> analysis<span class="token punctuation">.</span>exports<span class="token punctuation">,</span>
      rawDeclarations<span class="token operator">:</span> analysis<span class="token punctuation">.</span>rawDeclarations<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">...</span>
  <span class="token punctuation">}</span>
</code></pre></div><p>当组件在编译<code>@Component</code>装饰器的元数据时，会检查该组件是否已在 NgModule 中注册。如果已在某个模块中注册，则向<code>LocalModuleScopeRegistry</code>获取模块的编译范围，在该模块的编译范围内对其进行编译：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ComponentDecoratorHandler</span> <span class="token keyword">implements</span>
    <span class="token class-name">DecoratorHandler<span class="token operator">&lt;</span>Decorator<span class="token punctuation">,</span> ComponentAnalysisData<span class="token punctuation">,</span> ComponentResolutionData<span class="token operator">&gt;</span></span> <span class="token punctuation">{</span>
  <span class="token function">resolve</span><span class="token punctuation">(</span>node<span class="token operator">:</span> ClassDeclaration<span class="token punctuation">,</span> analysis<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>ComponentAnalysisData<span class="token operator">&gt;</span><span class="token punctuation">)</span><span class="token operator">:</span>
      ResolveResult<span class="token operator">&lt;</span>ComponentResolutionData<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    <span class="token comment">// 获取模块的作用域</span>
    <span class="token keyword">const</span> scope <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>scopeReader<span class="token punctuation">.</span><span class="token function">getScopeForComponent</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">...</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>scope <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span><span class="token operator">!</span>scope<span class="token punctuation">.</span>compilation<span class="token punctuation">.</span>isPoisoned <span class="token operator">||</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usePoisonedData<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 对模块的作用域中的信息进行处理</span>
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> dir <span class="token keyword">of</span> scope<span class="token punctuation">.</span>compilation<span class="token punctuation">.</span>directives<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>dir<span class="token punctuation">.</span>selector <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          matcher<span class="token punctuation">.</span><span class="token function">addSelectables</span><span class="token punctuation">(</span>CssSelector<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span>selector<span class="token punctuation">)</span><span class="token punctuation">,</span> dir <span class="token keyword">as</span> MatchedDirective<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">const</span> pipes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> Reference<span class="token operator">&lt;</span>ClassDeclaration<span class="token operator">&gt;&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> pipe <span class="token keyword">of</span> scope<span class="token punctuation">.</span>compilation<span class="token punctuation">.</span>pipes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        pipes<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>pipe<span class="token punctuation">.</span>name<span class="token punctuation">,</span> pipe<span class="token punctuation">.</span>ref<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
      <span class="token operator">...</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>在获取到作用域之后，接下来组件会使用<code>R3TargetBinder</code>绑定组件模板 AST，这些内容会在 Ivy 编译器部分进行更多的介绍。</p> <p>默认情况下，NgModule 都是急性加载的，也就是说它会在应用加载时尽快加载，所有模块都是如此，无论是否立即要用。对于带有很多路由的大型应用，考虑使用惰性加载：一种按需加载 NgModule 的模式。惰性加载可以减小初始包的尺寸，从而减少加载时间。</p> <p>要惰性加载 Angular 模块，则需要用到<code>AppRoutingModule</code>，同时惰性加载还支持预加载的能力。</p> <h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>在 Angular 中，使用模块是最佳的组织方式。模块提供了聚焦于特定应用需求的一组功能，可以把应用划分成一些聚焦的功能区，比如用户工作流、路由或表单。</p> <p>对于 NgModule 模块，可以通过模块提供的服务以及共享出的组件、指令和管道来与根模块和其它 NgModule 模块进行合作。通过设置模块的导入和导出，Angular 可以解析出各个模块间的依赖关系。Angular 模块之间不允许出现循环依赖，因此一个 Angular 应用中的模块最终是呈现为以根模块为根节点的树状结构的。</p> <h3 id="参考"><a href="#参考" class="header-anchor">#</a> 参考</h3> <ul><li><a href="https://angular.cn/guide/ngmodules" target="_blank" rel="noopener noreferrer">Angular-NgModules<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-others/deep-into-angular/angular-design-module.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html" class="prev">
          4.Zone区域之ngZone
        </a></span> <span class="next"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html">
          6.依赖注入的基本概念
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-4174" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty0.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/111.225c257a.js" defer></script>
  </body>
</html>
